/**
 * 
 */
package truerefactor.output;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EFactory;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;

import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.MethodNode;
import truerefactor.graph.PackageNode;
import truerefactor.graph.TypePair;

/**
 * GraphToUML - The class that actually performs the conversion between a
 * CodeGraph and EMF and XMI
 * 
 * @author Isaac Griffith
 */
public class GraphToUML {

    /**
     * The factory instance which will generate the objects needed by EMF to
     * create the XMI file
     */
    private EcoreFactory coreFactory = EcoreFactory.eINSTANCE;
    /**
     * The core package of the XMI document
     */
    private EcorePackage corePackage = EcorePackage.eINSTANCE;

    /**
     * Creats a new EObject for the CodeGraph provided
     * 
     * @param graph
     *            graph to convert
     * @return EObject representation of the provided Graph
     */
    public EObject getEObject(CodeGraph graph)
    {
        List<PackageNode> packages = graph.getPackages();
        List<ClassNode> classes = graph.getClasses();
        // EcoreFactory coreFactory = EcoreFactory.eINSTANCE;
        // EcorePackage corePackage = EcorePackage.eINSTANCE;

        Map<String, EPackage> packageMap = new HashMap<String, EPackage>();
        Map<String, EClass> classMap = new HashMap<String, EClass>();

        // Create core model elements
        // 1. Create EPackage objects for each package
        createEPackages(packageMap, packages);

        // 2. Create EClass objects for each class
        createEClasses(packageMap, classMap, classes);

        // 3. Connect Classes together
        // 4. Fill-in information for each Class
        // a. Fill in attributes
        // b. Fill in methods
        createEAttribues(packageMap, classMap, classes);
        createEOperations(packageMap, classMap, classes);

        // 5. Create Class Hierarchy
        createClassHierarchy(classMap, classes);

        return null;
    }

    /**
     * Generates the EMF representations of packages in the graph
     * 
     * @param packageMap
     *            Package Mappings
     * @param packages
     *            List of all packages in the graph
     */
    public void createEPackages(Map<String, EPackage> packageMap, final List<PackageNode> packages)
    {
        for (PackageNode node : packages)
        {
            EPackage temp = coreFactory.createEPackage();

            String name = node.getIdentifier();
            String nsPrefix = node.getBase();

            temp.setName(name);
            temp.setNsPrefix(nsPrefix);
            temp.setNsURI("http://" + node.getBase() + "." + node.getIdentifier());

            packageMap.put(node.getIdentifier(), temp);
        }

        for (PackageNode node : packages)
        {
            EPackage temp = packageMap.get(node.getIdentifier());
            if (temp != null)
            {
                for (PackageNode sub : node.getSubPackages())
                {
                    EPackage subTemp = packageMap.get(sub.getIdentifier());
                    if (subTemp != null)
                    {
                        temp.getESubpackages().add(subTemp);
                    }
                }
            }
        }

        EPackage temp = coreFactory.createEPackage();
        temp.setName("");
        temp.setNsPrefix("");
        temp.setNsURI("http://default");
        packageMap.put("*DEFAULT*", temp);
    }

    /**
     * Generates the EMF representation of all the classes in the graph
     * 
     * @param packageMap
     *            Package Mappings
     * @param classMap
     *            Class Mappings
     * @param classes
     *            list of classes in the graph
     */
    public void createEClasses(final Map<String, EPackage> packageMap, Map<String, EClass> classMap,
            final List<ClassNode> classes)
    {
        for (ClassNode node : classes)
        {
            EClass temp = coreFactory.createEClass();
            temp.setName(node.getClassName());
            temp.setAbstract(node.isAbstract());
            temp.setInterface(node.isInterface());

            EPackage pkg = null;
            if (node.getPackage().equals(PackageNode.getDefaultPackage()))
                pkg = packageMap.get("*DEFAULT*");
            else
            {
                pkg = packageMap.get(node.getPackage().getIdentifier());
            }

            if (pkg != null)
                pkg.getEClassifiers().add(temp);
            else
                System.out.println("Package is null");

            classMap.put(node.getIdentifier(), temp);
        }
    }

    /**
     * Generates a usable type for EMF
     * 
     * @param type
     *            type name
     * @param isPrimitive
     *            true if the type if primitive, false otherwise
     * @return The EClassifier instance representing the type provided.
     */
    private EClassifier getType(String type, boolean isPrimitive)
    {
        EClassifier retVal = null;
        Class cl = corePackage.getClass();

        if (isPrimitive && type.length() >= 1)
        {
            String t = type.substring(0, 1);
            t = t.toUpperCase();
            type = t.concat(type.substring(1));
        }

        try
        {
            Method m = cl.getMethod("getE" + type, null);
            Object o = m.invoke(corePackage, null);
            if (o instanceof EClassifier)
            {
                retVal = (EClassifier) o;
            }
        }
        catch (SecurityException e)
        {
            // TODO Auto-generated catch block
            // e.printStackTrace();
        }
        catch (NoSuchMethodException e)
        {
            // TODO Auto-generated catch block
            // e.printStackTrace();
        }
        catch (IllegalArgumentException e)
        {
            // TODO Auto-generated catch block
            // e.printStackTrace();
        }
        catch (IllegalAccessException e)
        {
            // TODO Auto-generated catch block
            // e.printStackTrace();
        }
        catch (InvocationTargetException e)
        {
            // TODO Auto-generated catch block
            // e.printStackTrace();
        }

        return retVal;
    }

    /**
     * Creates the class hierarchy in EMF objects
     * 
     * @param classMap
     *            Class Mappings
     * @param classes
     *            List of classes in the graph
     */
    public void createClassHierarchy(Map<String, EClass> classMap, List<ClassNode> classes)
    {
        for (ClassNode node : classes)
        {
            List<ClassNode> parents = node.getParentClasses();
            for (ClassNode parent : parents)
            {
                if (parent == null)
                    continue;

                EClass ecParent = classMap.get(parent.getClassName());
                EClass ecSubClass = classMap.get(node.getClassName());

                if (ecParent != null && ecSubClass != null)
                    ecSubClass.getESuperTypes().add(ecParent);
            }
        }
    }

    /**
     * Generates the EMF Attributes
     * 
     * @param packageMap
     *            Package Mappings
     * @param classMap
     *            Class Mappings
     * @param classes
     *            List of classes in the graph
     */
    public void createEAttribues(final Map<String, EPackage> packageMap, final Map<String, EClass> classMap,
            final List<ClassNode> classes)
    {
        for (ClassNode node : classes)
        {
            EClass eClass = classMap.get(node.getIdentifier());
            List<CodeNode> attrs = node.getAttributes();

            if (eClass == null)
                continue;

            for (CodeNode attr : attrs)
            {
                if (attr instanceof AttributeNode)
                {
                    AttributeNode attribute = (AttributeNode) attr;

                    if (classMap.containsKey(attribute.getType()))
                    {
                        EReference temp = coreFactory.createEReference();
                        String attrName = attr.getIdentifier();
                        if (attrName.contains(".")) {
                            int index = attrName.lastIndexOf(".");
                            attrName = attrName.substring(index + 1);
                        }
                        temp.setName(attrName);
                        temp.setEType(classMap.get(attribute.getType()));

                        eClass.getEStructuralFeatures().add(temp);
                    }
                    else
                    {
                        EAttribute temp = coreFactory.createEAttribute();
                        String attrName = attr.getIdentifier();
                        if (attrName.contains(".")) {
                            int index = attrName.lastIndexOf(".");
                            attrName = attrName.substring(index + 1);
                        }
                        temp.setName(attrName);

                        EClassifier classifier = getType(attribute.getType(), attribute.isPrimitive());
                        if (classifier != null)
                        {
                            temp.setEType(classifier);
                        }

                        eClass.getEStructuralFeatures().add(temp);
                    }
                }
            }
        }
    }

    /**
     * Generates the necessary EOperations to facilitate UML Generation
     * 
     * @param packageMap
     *            Package Mappings
     * @param classMap
     *            Class Mappings
     * @param classes
     *            list of classes in the graph
     */
    public void createEOperations(final Map<String, EPackage> packageMap, final Map<String, EClass> classMap,
            final List<ClassNode> classes)
    {
        for (ClassNode node : classes)
        {
            EClass eClass = classMap.get(node.getIdentifier());
            List<CodeNode> ops = node.getMethods();

            if (eClass == null)
                continue;

            for (CodeNode op : ops)
            {
                if (op instanceof MethodNode)
                {
                    MethodNode operation = (MethodNode) op;
                    EOperation temp = coreFactory.createEOperation();
                    
                    String opName = operation.getIdentifier();
                    if (opName.contains("."))
                        opName = opName.substring(opName.lastIndexOf(".") + 1);
                    
                    temp.setName(opName);

                    if (!operation.getReturnType().equals("void"))
                    {
                        EClassifier classifier = getType(operation.getReturnType(), operation.isReturnPrimitive());
                        if (classifier != null)
                        {
                            temp.setEType(classifier);
                        }
                    }

                    for (String param : operation.getParameters())
                    {
                        String name = param;
                        if (param.contains("."))
                            name = name.substring(name.lastIndexOf(".") + 1);
                        EParameter eParam = coreFactory.createEParameter();
                        eParam.setName(name);
//                        TypePair paramTypeDef = operation.getParamInfo(name);
//                        eParam.setEType(getType(paramTypeDef.getType(), paramTypeDef.isPrimitive()));
                        temp.getEParameters().add(eParam);
                    }

                    eClass.getEOperations().add(temp);
                }
            }
        }
    }

    /**
     * Actually generates the XMI file
     * 
     * @param writeTo
     *            file location to write t
     * @param packageMap
     *            PackageMappings
     * @param classMap
     *            ClassMappings
     */
    public void generateOutput(String writeTo, Map<String, EPackage> packageMap, Map<String, EClass> classMap)
    {
        ResourceSet metaResourceSet = new ResourceSetImpl();
        metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put("*", new XMIResourceFactoryImpl());
        // metaResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION,
        // new ResourceFactoryImpl()
        // {
        // public XMIResourceImpl createResource(URI uri) {
        // return new XMIResourceImpl(uri) {
        // protected boolean useUUIDs() { return true; }
        // };
        // }
        // });

        Resource metaResource = metaResourceSet.createResource(URI.createFileURI(writeTo));

        EFactory factoryInstance = corePackage.getEFactoryInstance();

        for (String pkg : packageMap.keySet())
        {
            metaResource.getContents().add(packageMap.get(pkg));
        }

        for (String cl : classMap.keySet())
        {
            // metaResource.getContents().add(classMap.get(cl));
            EClass ecl = classMap.get(cl);
            // EObject eobj = factoryInstance.create(ecl);

            metaResource.getContents().add(ecl);
        }

        try
        {
            // Save the resource
            metaResource.save(null);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
}
